class MyPromise {
  constructor(excute) {
    // excute是同步执行先
    this.value = "";
    this.reason = "";
    this.state = "pending";
    this.onFulfilledFns = [];
    this.onRejectedFns = [];

    // resolve,和reject 方法在下一个事件循环当中执行
    const resolve = (value) => {
      // 可以用 queueMicrotask 代替settimeout
      // setTimeout(() => {
      //   if (this.state === "pending") {
      //     this.state = "fulfilled";
      //     this.value = value;
      //     this.onFulfilledFns.forEach((fn) => {
      //       fn(value);
      //     });
      //   }
      // });

      queueMicrotask(() => {
        if (this.state === "pending") {
          this.state = "fulfilled";
          this.value = value;
          this.onFulfilledFns.forEach((fn) => {
            fn(value);
          });
        }
      });
    };

    const reject = (reason) => {
      setTimeout(() => {
        if (this.state === "pending") {
          this.state = "rejected";
          this.reason = reason;
          this.onRejectedFns.forEach((fn) => {
            fn(reason);
          });
        }
      });
    };

    try {
      excute(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  /**
   * then方法接收promise执行之后的值，入参是两个回调函数，分别接执行成功和执行失败的的值，两个回调函数也要在下一轮事件循环中执行，所以要用到settimeout;
   * then方法支持链式调用，返回的值是一个新的promsie实例。
   * @param {*} onResolve
   * @param {*} onReject
   */
  then(onResolve, onReject) {
    let promise;
    let x;

    promise = new MyPromise((resolve, reject) => {
      if (this.state === "fulfilled") {
        setTimeout(() => {
          try {
            x = onResolve(this.value);
            resolvePromise(promise, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else if (this.state === "rejected") {
        setTimeout(() => {
          try {
            x = onReject(this.reason);
            resolvePromise(promise, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else if (this.state === "pending") {
        this.onFulfilledFns.push(() => {
          setTimeout(() => {
            try {
              x = onResolve(this.value);
              resolvePromise(promise, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedFns.push(() => {
          setTimeout(() => {
            try {
              x = onReject(this.reason);
              resolvePromise(promise, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });

    return promise;
  }
  /**
   * catch方法返回一个Promise, 并处理拒绝的情况，它的行为其实与 Promise.prototype.then(()=>{}, (err)=>{dosometing})相同。 (事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)).
   */
  catch(onError) {
    this.then(
      () => {},
      (err) => {
        onError(err);
      }
    );
  }
  /**
   * finally() 方法返回一个Promise。在promise结束时，无论结果是fulfilled或者是rejected，都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
   * 这避免了同样的语句需要在then()和catch()中各写一次的情况。
   * @param {*} onFinally
   */
  finally(onFinally) {
    this.then(
      (value) => {
        return MyPromise.resolve(onFinally()).then(() => {
          return value;
        });
      },
      (reason) => {
        return MyPromise.reject(onFinally()).then(
          () => {},
          () => {
            throw reason;
          }
        );
      }
    );
  }
}
/**
 * MyPromise.resolve方法返回一个解析完参数后的promise,可以强制在下一次事件循环后使用传入的参数值
 * @param {*} target
 */
MyPromise.resolve = (target) => {
  if (target instanceof MyPromise) {
    // 是promise实例的话就直接返回
    return target;
  }
  return new MyPromise((resolve) => {
    resolve(target);
  });
};

/**
 * reject 方法返回一个带有拒绝原因的 MyPromise对象，跟MyPromise.resolve有点像
 * @param {*} reason
 */
MyPromise.reject = (reason) => {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};

/**
 *  入参接收一个promise的iterable类型（Array, Map，Set等都行），等待每个promise执行完成之后，resolve一个新数组，新数组是每个promise执行后的结果，不按顺序来，如果执行报错了则reject第一个报错的信息，resolve的东西就没了
 * @param {*} promises
 */
MyPromise.all = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (promises.length === 0) {
      resolve([]);
    }

    let result = [];
    for (let i = 0; i < promises.length; i++) {
      let task = promises[i];
      MyPromise.resolve(task).then(
        (val) => {
          result[i] = val;
        },
        (err) => {
          return reject(err);
        }
      );
    }

    resolve(result);
  });
};

/**
 * 同样接收一个promise的iterable类型，这个方法resolve第一个执行完的promise的结果，或者reject第一个完成的promise失败的原因
 * @param {*} promises
 */
MyPromise.race = (promises) => {
  return new MyPromise((resolve, reject) => {
    if (promises.length === 0) {
      resolve([]);
    }
    for (let i = 0; i < promises.length; i++) {
      let task = promises[i];
      MyPromise.resolve(task).then(
        (val) => {
          return resolve(val);
        },
        (reason) => {
          return reject(reason);
        }
      );
    }
  });
};

/**
 * 跟all方法差不多，入参也接收一个promise的iterable类型， 不过是跟all方法反着来，只resolve第一个执行成功的结果，执行都失败的话则reject一个数组，数组里是每个promise的失败原因
 * @param {*} promises
 */
MyPromise.any = (promises) => {
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      resolve([]);
    }

    let reasons = [];
    for (let i = 0; i < promises.length; i++) {
      let task = promises[i];
      MyPromise.resolve(task).then(
        (val) => {
          return resolve(val);
        },
        (err) => {
          reasons[i] = err;
        }
      );
    }

    reject(reasons);
  });
};

/**
 * 处理执行完成，根据onResolve或者onReject返回的值x进行处理
 * @param {*} promise
 * @param {*} x
 * @param {*} resolve
 * @param {*} reject
 */
function resolvePromise(promise, x, resolve, reject) {
  // 当x === promise的时候，会造成死循环，当x === promise的时候,x的状态一直是pending,不会变化，执行then方法的时候只会收集依赖，不会触发resolev或者reject,所以要禁止这个情况
  if (x === promise) {
    throw new Error("x不能和promise一样");
  } else if (x !== null && (typeof x === "function" || typeof x === "object")) {
    // 当x为对象或函数时, thenable对象
    let excuted = false; // 加一个变量锁着，只执行一次
    try {
      const then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (excuted === false) {
              resolve(y);
              excuted = true;
            }
          },
          (err) => {
            if (!excuted) {
              reject(y);
              excuted = true;
            }
          }
        );
      } else {
        resolve(x);
      }
    } catch (err) {
      if (!excuted) {
        reject(err);
        excuted = true;
      }
    }
  } else {
    resolve(x);
  }
}
